Mike Brady (author of Open Prolog): brady@cs.tcd.ie
Physical Address: Department of Computer Science, Trinity College, Dublin, Ireland.
Open Prolog FTP site: grattan.cs.tcd.ie (134.226.32.15).
Subdirectory: pub/languages/open-prolog
• General
Open Prolog's External Predicate Interface enables programmers to add procedural predicates, called External Predicates, to Open Prolog without needing access to Open Prolog's source code or link libraries.
External Predicates are contained in PRLX resources; a PRLX resource can contain one or more external predicates.
All External Predicates that have been written so far have been written in MPW Pascal and debugged using SADE. Although you may find these products 'clunky', they are very powerful and quite reliable. However, you'll need a 68020/30/40, a Hard Disk and 8MB RAM to get anywhere with them.
The interface, library and make files, together with the samples, have all been written for MPW 3.2.
• Modes of Operation.
External Predicates are contained in PRLX resources and normal code execution begins at the first byte of the resource.
External Predicate code can execute under three distinct sets of conditions, or modes; they are (1) Normal Operation, (2) Window-Driven Operation, (3) Event-Driven Operation.
IMPORTANT: If your predicate has Window-Driven or Event-Driven Operation modes, then the PRLX resource containing it must be locked and preloaded. If you aren't using MPW, then you can set the lock and preload bits using ResEdit. If you don't lock such predicates, then the code will be moved around during execution, leading to dangling pointers. If preload isn't chosen for locked resources, you'll get memory fragmentation and 'Out of Memory' errors.
◊ Normal Operation
In Normal Operation, the PRLX code is always called as a function to which a parameter list of type prlxRecord (generally called plist) is passed. The parameter list is the channel through which data and commands can be passed between Open Prolog and the predicate. Callback commands are available to allow the predicate to query Open Prolog. While a predicate is active, Open Prolog is suspended. Callback commands are generally available during the Call Phase of Normal Operation.
There are three distinct phases of normal operation of external predicates:
(1) Initialisation; (2) Call; (3) Closedown. Closedown is not currently implemented.
° Initialisation Phase:
When Open Prolog starts up, it executes each PRLX resource, first asking how many predicates it contains, then initialising each one in turn. During initialisation each predicate gives its name, arity and permanent data (one longword). Thereafter, the permanent data will always be available in plist.permanentData during normal operation. Typically, the permanent data will be a handle to data structures used by the predicate. For backward compatibility, data stored in plist.data[2] is also permanent; please don't use this feature in future, as it may disappear.
External Predicate interfaces prior to Open Prolog 1.0d37 didn't provide for predicates that wanted to share data structures among themselves - for example, if a PRLX resource contained a set of predicates all related to file handling, there was no way to allow those predicates to have access to a common data structure describing their file structures. Effective from Open Prolog 1.0d37, while the predicates in the same PRLX resource are being initialised, you are guaranteed that plist.data[3] will retain whatever data is placed in it *across initialisation calls*. For example, suppose there are two predicates in a PRLX resource - foo and bar - and let's say foo is the first predicate and bar is the second. During initialisation, OP will call foo and tell it to initialise itself. Foo could put the handle to a common data structure in plist.data[3] (as well as into its plist.permanentData field). Now, when bar is initialised, plist.data[3] will still contain the handle placed in it by the initialisation of foo. It could then be transferred into bar's plist.permanentData field if desired. In this way, foo and bar could refer to the same data structure.
Be aware that this guarantee of the safekeeping of values in plist.data[3] is only effective across initialisation calls to the predicates within a PRLX resource.
° Call Phase:
This phase occurs when the predicate is called during execution of a Prolog program. Generally the predicate will get and return values via the parameter list. The predicate indicates whether a successful or unsuccessful outcome should result, and also whether the [successful] outcome is determinate. Currently, only determinate outcomes are supported. Effective from Open Prolog 1.0d37, a predicate can return an error code and associated error information. The error code is a number corresponding to an ISO Prolog Draft error type (system_error, domain_error, etc. with two additions - see prlxDefinitions.p). The error information can be any Prolog term. When an error has been signalled, upon subsequent exit from the predicate, an error message of the following form is thrown:
error(<error type>,<error information term>).
In the prlxLibraries accompanying this release, there is a routine:
The argument_index and host_error_code terms are omitted if the numbers are zero, and the message term is omitted if the message string is empty. The goal is omitted if the argument index is -1.
° Closedown Phase:
This is supposed to tell each predicate to finish its business prior to quitting Prolog. It hasn't been implemented yet.
◊ Window Driven Operation
If your predicate 'owns' any windows, then it must be capable of responding to window-related events. Whenever Open Prolog responds to a window-related event, it calls the function whose address is in the refCon field of the window concerned. You must supply this function for your window(s). The parameter scheme for the function must have the following form:
FUNCTION foo(theWindow: windowPtr;
parameter: longint;
message: integer): longint;
The theWindow field will have the address of the window concerned. The parameter field may have relevant information (e.g. Menu Coordinates). Great documentation, isn't it?
The message field contains an event… code indicating the kind of event. Look in prlxDefinitions.p for the codes. Usually you return 'messageOK' for the function's value. At a very minimum, your function should respond to eventUpdate and eventActivate (& deactivate) events.
Window-driven operation occurs asynchronously with respect to the operation of Open Prolog, so callback commands are not available during window-driven operation.
◊ Event Driven Operation
You can nominate a function to see events coming in to Open Prolog before they are processed. This is done by executing a 'sendEvents' callback command while your predicate is initialising. (See the example countHLE.p.) You must provide the address of a function in callbackData[1] and you can provide permanent data - 'yourData' - in callbackData[2]. Then, for every event, your function will be called, according to the following scheme:
FUNCTION foo(yourData:longint;theEvent:eventPtr):longint;
° yourData is a longint. Notice that it's not a VAR parameter, so you can't alter it directly. Typically, it would be a handle to a data structure.
° theEvent is a pointer to the event record. You can examine the fields, but don't change any of them because they are used by Open Prolog itself.
If you want normal processing of the event to occur after your function has seen it, then return the value 'messageNoReply'. If your function takes care of it completely, and nothing further needs to be done by Open Prolog, then return 'messageOK'. If your function returns 'messageQuit', or 'messageError', guess what?
If you are handling high-level events using an external predicate, be careful not to send bogus replies to the events that your handler doesn't process. Apparently, from IM V6, if you dispatch to your HLE code in the approved manner, and if the dispatch table has no entry for the particular event, then a message will be sent back to the sender indicating the event can't be handled. This is inappropriate in the context of Open Prolog, because OP itself might handle it. So the message is: only accept for processing those events you are certain you can handle.
Similarly to window-driven operation, event-driven operation occurs asynchronously with respect to the operation of Open Prolog, so callback commands are not available during event-driven operation.
Note: only one external predicate can be nominated for receiving events. Make sure there are no others (use ResEdit) - you'll need to remove the PRLX resource number 138, called countHLE, when you start building your own.
• The Examples
A good way to start might be to copy an existing example. There are three: soundPlay.p,
countHLE.p and draw.p.
◊ soundPlay is the simplest example. It plays any 'snd ' resource available (e.g. the sounds available in the Sound Control Panel). It doesn't have any window or event driven operation.
◊ countHLE is a simple predicate to count the number of core appleEvents that pass through the program. It defines a function which is called for every event. The function communicates with the predicate through a shared data structure.
◊ OpenPrologSpeech contains two simple predicates to use the Sound Manager.
◊ draw.p is a fairly complex predicate that implements the drawing commands described in the release.
If you use a copy of the example as the starting point for your predicate, copy the makefile entry too - just be careful to give your PRLX segment a new ID number. See also the note above about receiving events.